#include <47func.h>
#include <ctype.h>

typedef struct msg
{
    char command[10];
    int cmdType;
    char arg[100];
} MSG;

typedef struct train_s
{
    int length;
    char data[1000]; // 上限是1000个字节
} train_t;

typedef enum taskType
{
    CD = 1,
    LS = 2,
    PUTS = 3,
    GETS = 4,
    REMOVE = 5,
    PWD = 6,
    MKDIR = 7,
    RMDIR = 8,
    EXIT = 9,
    MYLS = 10, // 本地执行ls
    MYCD = 11  // 本地执行cd
} TaskType;

off_t getOffset(int netfd, const char *filename)
{
    char findFilename[50] = {0};
    strcpy(findFilename, filename);

    // 给文件名加后缀
    strcat(findFilename, ".filepart");

    // 判断当前路径是否存在同名文件
    bool exist = false;
    int ret = open(findFilename, O_RDONLY);
    if (ret != -1)
    {
        exist = true;
    }

    // 如果存在同名需要断点续传的文件
    if (exist == true)
    {
        int fd = open(findFilename, O_RDWR);
        // 获取文件大小
        struct stat statbuf;
        fstat(fd, &statbuf);
        return statbuf.st_size;
    }
    else
    {
        return 0;
    }
}

// 读入一行字符串，用来读命令
int readLine(char *str)
{
    int num = 0;
    char ch;
    while (num < 100 && (ch = getchar()) != '\n')
    {
        str[num++] = ch;
    }
    str[num] = '\0';
    return num;
}

int recvn(int sockfd, void *buf, int length)
{
    int total = 0;
    int ret;
    char *p = (char *)buf;
    while (total < length)
    {
        ret = recv(sockfd, p + total, length - total, 0);
        total += ret;
    }
    return 0;
}

// 下载文件
int getFile(int sockfd, char *filename, off_t offset)
{
    char oriFilename[50] = {0};
    strcpy(oriFilename, filename);

    train_t train;
    bzero(&train, sizeof(train));
    strcat(filename, ".filepart");
    int fd = open(filename, O_RDWR | O_CREAT, 0666);
    lseek(fd, offset, SEEK_SET);
    off_t filesize;
    recvn(sockfd, &train.length, sizeof(train.length));
    recvn(sockfd, &filesize, train.length);
    printf("filesize = %ld\n", filesize);
    off_t cursize = 0;
    off_t lastsize = 0;
    off_t slice = filesize / 10000;
    while (1)
    {
        bzero(&train, sizeof(train));
        // recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);//先收四个字节
        recvn(sockfd, &train.length, sizeof(train.length)); // 先收四个字节
        if (train.length != 1000)
        {
            printf("train.length = %d\n", train.length);
        }
        if (train.length == 0)
        {
            break;
        }
        // recv(sockfd,train.data,train.length,MSG_WAITALL);//再一个train.length的长度
        recvn(sockfd, train.data, train.length); // 再一个train.length的长度
        write(fd, train.data, train.length);
        cursize += train.length;
        if (cursize - lastsize > slice)
        {
            printf("%5.2lf%%\r", 100.0 * cursize / filesize);
            fflush(stdout);
            lastsize = cursize;
        }
    }
    printf("100.00%%\n");
    rename(filename, oriFilename);
    close(fd);
    return 0;
}

// 发送文件
int putFile(int sockfd, char *filename, off_t offset)
{
    train_t train;
    int fd = open(filename, O_RDWR);
    lseek(fd, offset, SEEK_SET);
    struct stat statbuf;
    // 获取文件大小
    fstat(fd, &statbuf);
    // 发送小火车 火车头 size的长度 火车车厢 size的内容
    bzero(&train, sizeof(train));
    train.length = sizeof(statbuf.st_size);
    off_t realSize = statbuf.st_size - offset;
    memcpy(train.data, &realSize, train.length);
    send(sockfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);

    if (statbuf.st_size > 100 * 1024 * 1024)
    {
        // 如果文件大于100M，使用mmap
        char *p = (char *)mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        ERROR_CHECK(p, MAP_FAILED, "mmap");
        off_t totalsize = 0; // 记录已经发送的长度
        while (totalsize < statbuf.st_size)
        {
            if (statbuf.st_size - totalsize > (long)sizeof(train.data))
            {
                train.length = sizeof(train.data); // 剩余数据超过火车车厢的上限
            }
            else
            {
                train.length = statbuf.st_size - totalsize;
            }
            send(sockfd, &train.length, sizeof(train.length), MSG_NOSIGNAL);
            send(sockfd, p + totalsize, train.length, MSG_NOSIGNAL);
            totalsize += train.length;
        }
        munmap(p, statbuf.st_size);
    }
    else
    {
        // printf("普通发送\n");
        // 小于100M，普通发送
        while (1)
        {
            // 将文件的数据读取到小火车的车厢部分
            ssize_t sret = read(fd, train.data, sizeof(train.data));
            train.length = sret;
            // 发送小火车
            send(sockfd, &train, sizeof(train.length) + train.length, MSG_NOSIGNAL);
            if (sret == 0)
            {
                break;
            }
        }
    }
    // 补发一个空火车
    train.length = 0;
    send(sockfd, &train.length, sizeof(train.length), MSG_NOSIGNAL);
    close(fd);
    return 0;
}

void commandParser(int sockfd)
{
    MSG msg; // 接收客户端命令
    char str[64];
    char *token;
    int argCount;
    train_t train;
    char buf[4096];
    while (readLine(str))
    {
        token = NULL;
        memset(&train, 0, sizeof(train_t));
        memset(&msg, 0, sizeof(MSG));
        memset(&buf, 0, sizeof(buf));
        argCount = 0;
        token = strtok(str, " ");
        strcpy(msg.command, token);
        // puts(msg.command);
        token = strtok(NULL, " ");
        if (token)
        { // 获取命令后的参数
            strcpy(msg.arg, token);
            argCount++;
        }
        memset(str, 0, sizeof(str));
        printf("msg.command = %s\n", msg.command);
        printf("msg.arg = %s\n", msg.arg);
        printf("argCount = %d\n", argCount);
        // 开始解析命令
        if (strcmp(msg.command, "cd") == 0)
        { // 执行cd命令
            if (argCount == 0 || argCount > 1)
            { // cd后无参数或有多个参数  无效命令
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = CD;
                send(sockfd, &msg, sizeof(msg), 0);
                recv(sockfd, buf, sizeof(buf), 0);
                printf("%s\n", buf);
            }
        }
        else if (strcmp(msg.command, "ls") == 0)
        {                     // ls命令
            msg.cmdType = LS; // 纠正错误
            if (argCount == 0 || argCount == 1)
            {
                // 无参 给服务端发送显示当前目录的请求
                // 有一个参数，将命令发送给服务端，若命令合法则显示指定目录
                send(sockfd, &msg, sizeof(msg), 0);
                int ret = recv(sockfd, buf, sizeof(buf), 0);
                printf("ret = %d, buf = %s\n", ret, buf);
            }
            else
            {
                printf("args error\n");
                continue;
            }
        }
        else if (strcmp(msg.command, "puts") == 0)
        { // puts命令
            if (argCount != 1)
            { // puts后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = PUTS;
                send(sockfd, &msg, sizeof(msg), 0);
                putFile(sockfd, msg.arg, getOffset(sockfd, msg.arg));
            }
        }
        else if (strcmp(msg.command, "gets") == 0)
        { // gets命令
            if (argCount != 1)
            { // gets后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = GETS;
                send(sockfd, &msg, sizeof(msg), 0);
                getFile(sockfd, msg.arg, getOffset(sockfd, msg.arg));
            }
        }
        else if (strcmp(msg.command, "remove") == 0)
        { // remove命令
            if (argCount != 1)
            { // remove后仅可接收一个参数
                printf("args error\n");
                continue;
            }
            else
            {
                // 将命令交给服务端执行
                msg.cmdType = REMOVE;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "pwd") == 0)
        { // pwd命令
            if (argCount != 0)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                // 将请求发送给服务端执行
                msg.cmdType = PWD;
                send(sockfd, &msg, sizeof(msg), 0);
                // 此处有问题
                int ret = recv(sockfd, buf, sizeof(buf), 0);
                printf("ret = %d, pwd: %s\n", ret, buf);
            }
        }
        else if (strcmp(msg.command, "mkdir") == 0)
        { // mkdir命令
            if (argCount != 1)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                msg.cmdType = MKDIR;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "rmdir") == 0)
        { // rmdir命令
            if (argCount != 1)
            {
                printf("args error\n");
                continue;
            }
            else
            {
                // 请求交给服务器  需要哦安短目录是否为空
                msg.cmdType = RMDIR;
                send(sockfd, &msg, sizeof(msg), 0);
            }
        }
        else if (strcmp(msg.command, "exit") == 0)
        {
            // exit模块目前暂时有问题 但可用
            // if(argCount != 0)
            //     continue;
            msg.cmdType = EXIT;
            send(sockfd, &msg, sizeof(msg), 0);
            // recv(sockfd, buf, sizeof(buf), 0);
            // printf("%s\n", buf);
            exit(0);
        }
        else if (strcmp(msg.command, "myls") == 0)
        {
            system("ls");
            continue;
        }
        else if (strcmp(msg.command, "mycd") == 0)
        {
            if (argCount == 0)
            { // 非法参数
                printf("argc error!\n");
            }
            else
            {
                int ret = chdir(msg.arg);
                if (ret == 0)
                { // 执行成功
                    getcwd(buf, sizeof(buf));
                    printf("cwd: %s\n", buf);
                }
                else
                { // 执行失败
                    printf("No such directory or file!\n");
                }
            }
            continue;
        }
        else
        {
            continue;
        }
    }
}



int main(int argc, char *argv[])
{
    // ./client 192.168.118.128 1234 filename

    ARGS_CHECK(argc, 3);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serverAddr.sin_port = htons(atoi(argv[2]));
    int ret = connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    ERROR_CHECK(ret, -1, "connect");

    commandParser(sockfd);

    return 0;
}

